/*
 * BuilderExpert.cpp
 *
 *  Created on: Nov 20, 2008
 *      Author: Ryan
 */

#include "BuilderExpert.h"

#include "../UnitAnalyzers/UnitCostAnalyzer.h"
#include "../UnitAnalyzers/ExtractorAnalyzer.h"
#include "../UnitAnalyzers/BuilderExtractor.h"
#include "../UnitAnalyzers/ResourceExtractorFilter.h"
#include "../UnitAnalyzers/ResourceGeneratorFilter.h"
#include "../UnitAnalyzers/FactoryFilter.h"
#include "../UnitAnalyzers/FactoryAnalyzer.h"
#include "../UnitAnalyzers/BuildingFilter.h"
#include "../ResourceAnalysisUnit.h"

#include "../../../OOInterface/Objects/Events/UnitCreatedEvent.h"
#include "../../../OOInterface/Objects/Units/UnitAspects/BuilderAspect.h"
#include "../../../OOInterface/Objects/Logger.h"

void BuilderExpert::run()
{
	handleBufferedEvents();

	static int counter = 0;
	updateBuilderList();


	if(_idleBuilders.empty() )
		return; //No point in doing any building routines if all the builders are busy.


	ResourceExtractorFilter filter1;
	updateList( filter1, _extractors );
	ResourceGeneratorFilter filter2( _i->getResourceTypes() );
	updateList( filter2, _resourceGenerators );
	FactoryFilter filter3;
	updateList( filter3, _factories );



	const OOInterface::ResourceList& resources = _i->getResourceTypes();

	bool resourceSafeToBuild = true;

	for( OOInterface::ResourceList::const_iterator resource = resources.begin(); resource != resources.end(); ++resource )
	{
		float income = _self->getResourceIncome( *resource );
		float upkeep = _self->getResourceUsage( *resource );
		float storage = _self->getResourceStorage( *resource );
		float stored = _self->getResourceAmount( *resource );

		if( upkeep >= income * 0.75 || stored <= 0.25 * storage ) //Arbitrary constants of 75% and 25% at the moment
			//TODO: Tweak these values to be nicer or more sensical.
		{
			//Need to build some resource generators for this!
			//Favor extractors over generators!

			float maxRadius = 0.0f;
			for( OOInterface::ConstUnitSet::iterator extract = _extractors.begin(); extract != _extractors.end(); ++extract )
			{
				filter1.reset();
				(*extract)->accept( filter1 );

				if( filter1.isExtractor() )
					if( filter1.getExtractorAspect()->getRadius() > maxRadius )
						maxRadius = filter1.getExtractorAspect()->getRadius();
			}

			if( maxRadius > 0.0f)
			{

				/**
				const OOInterface::ConstUnitSet allBuiltBuildings = _infrastructure.getBuiltBuildings();

				ResourceNodes extractorLocations;

				for( OOInterface::ConstUnitSet::const_iterator it = allBuiltBuildings.begin(); it != allBuiltBuildings.end(); ++it )
				{
					filter1.reset();
					(*it)->accept(filter1);
					if( filter1.isExtractor() )
					{
						extractorLocations.push( (*it)->getLocation() );
					}
				}

				ResourceAnalysisUnit extractor(_i, *resource, (*_idleBuilders.begin())->getLocation(), 200.0f, extractorLocations, maxRadius);

				extractor.run();

				if(extractor.isDone() && !extractor.getResult().empty() )
				{
					ResourceNodes nodes = extractor.getResult();

					if( nodes.front().getX() == -1 && nodes.front().getZ() == -1)
						continue;

					OOInterface::ConstUnitPtr idealExtractor;
					ExtractorAnalyzer extractorAnalyzer( _i, _self );
					float maxExtractorRating = 0.0f;

					for( OOInterface::ConstUnitSet::iterator extract = _extractors.begin(); extract != _extractors.end(); ++extract )
					{
						extractorAnalyzer.reset( nodes.front() );
						(*extract)->accept( extractorAnalyzer );

						if( extractorAnalyzer.getRating() > maxExtractorRating && _infrastructure.canBuildAt( *extract, nodes.front() ))
						{
							maxExtractorRating = extractorAnalyzer.getRating();
							idealExtractor = (*extract);
						}
					}

					if( idealExtractor.get() )
					{
						_buildQueue.push( BuildingPointPair( idealExtractor, nodes.front() ) );
					}
				}
				**/
			}


			//find an ideal extractor/generator for this and insert it into a build queue.
			resourceSafeToBuild &= false;
		}
	}

	if( resourceSafeToBuild && _buildQueue.size() == 0 )
	{
		//Focus on Defenses and factories here (only if factories and defense are needed)

		//Possibly check if other buildings are in the process of being built and assist in their building

		FactoryAnalyzer factoryAnalyzer(_i, _self);

		float maxRating = 0.0f;
		OOInterface::ConstUnitPtr buildFactory;

		//int idlers = _idleBuilders.size() - _buildQueue.size();
		for( OOInterface::ConstUnitSet::iterator factory = _factories.begin(); factory != _factories.end(); ++factory )
		{
			factoryAnalyzer.reset();
			(*factory)->accept( factoryAnalyzer );

			//OOInterface::Logger::getInstance("AI/TAI/TAI.txt") << "FactoryAnalyzer Rating : "
			//<< factoryAnalyzer.getRating() << " for " << (*factory)->getName() << std::endl;

			if( factoryAnalyzer.getRating() > maxRating )
			{
				maxRating = factoryAnalyzer.getRating();
				buildFactory = (*factory);
			}

		}

		if( maxRating > 0.0f && buildFactory.get() )
		{
			pushOntoQueue( buildFactory );
		}

		//Anything that has been decided, put it into a build queue.
	}

	distributeBuildingJobs();

	++counter;
}


void BuilderExpert::updateBuilderList()
{
	_idleBuilders.clear();

	OOInterface::ConstUnitSet allUnits = _self->getUnits();

	//Sift out all the builder units from the AI's unit set.

	BuilderExtractor builderSifter;

	for( OOInterface::ConstUnitSet::iterator unit = allUnits.begin(); unit != allUnits.end(); ++unit )
	{
		builderSifter.reset();
		(*unit)->accept( builderSifter );

		if( builderSifter.isBuilder() && (*unit)->isIdle() && !_reservation->isBuilderReserved( *unit ) )
			_idleBuilders.insert( (*unit ) );
	}
}

void BuilderExpert::updateList( FilterVisitor& filter, OOInterface::ConstUnitSet& list )
{
	list.clear();

	BuilderExtractor builderSifter;

	for( OOInterface::ConstUnitSet::iterator builder = _idleBuilders.begin(); builder != _idleBuilders.end(); ++builder )
	{
		builderSifter.reset();
		(*builder)->accept( builderSifter );

		if( builderSifter.getAspect() )
		{
			const OOInterface::ConstUnitSet& buildable = builderSifter.getAspect()->getBuildableUnits();

			for( OOInterface::ConstUnitSet::const_iterator building = buildable.begin(); building != buildable.end(); ++building )
			{
				filter.reset();
				(*building)->accept( filter );

				if( !filter.isFiltered() )
					list.insert( (*building ) );
			}
		}

	}
}

void BuilderExpert::distributeBuildingJobs()
{
	//Pair up idle builders to a job that they can perform while minimizing distance traveled too

	BuilderExtractor builderSifter;

	OOInterface::CommandFactory& cmdFactory = _i->getCommandFactory();

	while( !_buildQueue.empty() )
	{
		BuildingPointPair pair = _buildQueue.front();

		OOInterface::ConstUnitPtr idleBuilder;

		for( OOInterface::ConstUnitSet::iterator builder = _idleBuilders.begin(); builder != _idleBuilders.end(); ++builder )
		{
			builderSifter.reset();
			(*builder)->accept( builderSifter );

			if( builderSifter.getAspect() && builderSifter.getAspect()->canBuild( pair.first ) )
			{
				idleBuilder = *builder;
				break;
			}
		}

		_infrastructure.destroyBuilding( pair.first );

		if( idleBuilder.get() )
		{


			_i->orderUnit( idleBuilder, cmdFactory.createBuildCommand( pair.first, pair.second) );

			_idleBuilders.erase( idleBuilder );
		}



		_buildQueue.pop();
	}

	while( !_calcBuildQueue.empty() )
	{
		if( !_idleBuilders.empty() )
		{
			OOInterface::ConstUnitPtr building = _calcBuildQueue.front();


			float closestBuilder = 100000000000000.0f;
			OOInterface::ConstUnitPtr closeBuilder;
			OOInterface::Point3f closeBuildLoc;
			for( OOInterface::ConstUnitSet::iterator idleBuilder = _idleBuilders.begin(); idleBuilder != _idleBuilders.end(); ++idleBuilder )
			{
				OOInterface::Point3f buildLoc = _infrastructure.findNearbyBuildLocation( building, (*idleBuilder)->getLocation() );
				float distanceTo = (*idleBuilder)->getLocation().distanceTo( buildLoc );
				if( distanceTo < closestBuilder )
					if( distanceTo == 0.0f )
					{
						UnitCostAnalyzer resourceCost(_i->getResourceTypes());
						building->accept( resourceCost );
						giveOrder( *idleBuilder, cmdFactory.createBuildCommand( building, buildLoc ), resourceCost.getCost() );

						_idleBuilders.erase( *idleBuilder );
						break;
					}
					else
					{
						closestBuilder = distanceTo;
						closeBuilder = *idleBuilder;
						closeBuildLoc = buildLoc;
					}
			}

			if( closeBuilder.get() )
			{
				UnitCostAnalyzer resourceCost(_i->getResourceTypes());
				building->accept( resourceCost );
				giveOrder( closeBuilder, cmdFactory.createBuildCommand( building, closeBuildLoc ), resourceCost.getCost() );
				_idleBuilders.erase( closeBuilder );
			}
		}

		_calcBuildQueue.pop();
	}


}

void BuilderExpert::giveOrder( OOInterface::ConstUnitPtr builder, OOInterface::ConstCommandPtr command, ResourceMap cost )
{
	_reservation->reserve( builder, command, cost );
	_i->orderUnit( builder , command );
}

void BuilderExpert::pushOntoQueue( OOInterface::ConstUnitPtr building )
{
	_calcBuildQueue.push( building );
}

void BuilderExpert::pushOntoQueue( OOInterface::ConstUnitPtr building, OOInterface::Point3f loc )
{
	if( _infrastructure.canBuildAt( building, loc ) )
	{
		_infrastructure.buildBuilding( building, loc );
		_buildQueue.push( BuildingPointPair(building, loc) );
	}
	else
	{
		OOInterface::Point3f buildLoc = _infrastructure.findNearbyBuildLocation( building, loc );

		if( buildLoc.getX() != -1 && buildLoc.getZ() != -1)
		{
			_infrastructure.buildBuilding( building, buildLoc );
			_buildQueue.push( BuildingPointPair(building, buildLoc) );
		}
	}
}

void BuilderExpert::handle( OOInterface::ConstGameEventPtr event )
{
	boost::mutex::scoped_lock lock( _mutex );
	_eventQueue.push( event );
}

void BuilderExpert::handleBufferedEvents()
{
	boost::mutex::scoped_lock lock( _mutex );
	while( !_eventQueue.empty() )
	{
		OOInterface::ConstGameEventPtr event = _eventQueue.front();

		const OOInterface::UnitCreatedEvent* creation = dynamic_cast< const OOInterface::UnitCreatedEvent* > ( event.get() );

		if( creation )
		{
			OOInterface::ConstUnitPtr createdUnit = creation->getCreatedUnit();

			BuildingFilter filter;

			createdUnit->accept( filter );

			if( filter.isBuilding() )
			{
				_infrastructure.buildBuilding( createdUnit );
			}

		}

		_eventQueue.pop();
	}
}


